package none.rg.gantt.model;


import java.util.*;


/**
 * Class representing the task. It is parent for tasks of special types.
 * It incorporates all data fields necessary for task and also provides
 * mechanisms for dealing with tasks and links between them.
 * @author Rodion Gorkovenko
 */
public class Task {


    /**
     * Mark of the task type used to distinguish simple tasks from groups and milestones.
     * It should be redefined in heirs. For any object it could be checked
     * via {@link #getSpecialType} or {@link #isOfSpecialType}.
     */
    public static final String SPECIAL_TYPE = "task";
    
    private Chart chart;
    private String title;
    private String specTypeThis;
    private int duration;
    private int start, finish;
    private int id;
    private boolean fixedDuration;
    private Critical critical;
    
    private Map<Link.Id, Link> preds = new HashMap<>();
    private Map<Link.Id, Link> succs = new HashMap<>();
    
    
    /**
     * Common public constructor for task. It also notifies the chart to add
     * the task into its internal map. Chart assigns the new Id to the task.
     * @param chart the chart to which the task belongs
     * @param title the title for the task (it is better to be unique)
     * @param duration duration of the task
     * @param fixedDuration whether task could last longer than specified duration
     */
    @SuppressWarnings("LeakingThisInConstructor")
    public Task(Chart chart, String title, int duration, boolean fixedDuration) {
        this.chart = chart;
        this.id = chart.fetchNextTaskId();
        this.title = title;
        this.duration = duration;
        this.fixedDuration = fixedDuration;
        this.start = 0;
        this.finish = start + duration;
        chart.notifyTaskAdded(this);
    } // Task
    
    
    /**
     * Simplified public constructor for tasks with fixed duration.
     * @param chart the chart to which the task belongs
     * @param title the title for the task (it is better to be unique)
     * @param duration duration of the task (fixed)
     */
    public Task(Chart chart, String title, int duration) {
        this(chart, title, duration, true);
    } // Task
    
    
    /**
     * Allows to find out the type of a task (simple task or group / milestone),
     * as defined by {@link #SPECIAL_TYPE} class field.
     * @return type token
     */
    public String getSpecialType() {
        
        if (specTypeThis == null) {
            try {
                specTypeThis = (String) getClass().getField("SPECIAL_TYPE").get(null);
            } catch (Exception e) {
                throw new Error("Special type not defined for object of class " + getClass());
            } // catch
        } // if
        
        return specTypeThis;
    } // getSpecialType
    
    
    /**
     * Checks whether the task is of one of the types passed as parameters
     * @param types names of types with which the type of the task should be compared
     * @return true if task type matches to one of mentioned types
     */
    public boolean isOfSpecialType(String... types) {
        for (String t : types) {
            if (getSpecialType().equals(t)) {
                return true;
            } // if
        } // for
        return false;
    } // isOfSpecialType
    
    
    /** Returns the chart to which the task belongs. */
    public Chart getChart() { return chart; }
    
    /** Returns the id given to the task by the chart. */
    public int getId() { return id; }
    
    /** Returns the title of the task */
    public String getTitle() { return title; }
    /** Allows to change the title of the task */
    public void setTitle(String s) { title = s; }
    
    /** Returns duration of the task */
    public int getDuration() { return duration; }
    
    /** Allows to change duration of the tasks (and call recalculation of chart) */
    public void setDuration(int v) {
        duration = v;
        chart.recalc();
    } // setDuration
    
    /** Checks whether task has fixed duration */
    public boolean isFixedDuration() { return fixedDuration; };
    
    /** Returns the starting time (after calculation) */
    public int getStart() { return start; }
    
    /** Used when calculating the chart to give the task its starting time. */
    protected void setStart(int v) { start = v; }
    
    /** Returns the finish time (after calculation) */
    public int getFinish() { return finish; }
    
    /** Used when calculating the chart to give the task its finish time. */
    protected void setFinish(int v) { finish = v; }
    
    /** Tells the relation of the task to the critical path */
    public Critical getCritical() { return critical; }
    
    /** Used when calculation to mark critical path */
    protected void setCritical(Critical c) { critical = c; }
    
    /**
     * Used by links when they are created, so that task would remember all
     * its predecessors.
     */
    protected void addPred(Link link) {
        preds.put(link.getIdPred(), link);
    } // addPred
    
    
    /**
     * Used by links when they are created, so that task would remember all
     * its successors.
     */
    protected void addSucc(Link link) {
        succs.put(link.getIdSucc(), link);
    } // addSucc
    
    
    /**
     * Removes the task and all links connected to it. Asks the chart to
     * delete itself from internal map.
     */
    public void remove() {
        
        for (Link link : new LinkedList<>(succs.values())) {
            link.remove();
        } // for
        for (Link link : new LinkedList<>(preds.values())) {
            link.remove();
        } // for
        
        chart.notifyTaskRemoved(this);
    } // remove
    
    
    /**
     * Allows to remove link to predecessor
     * @param pred predecessor task
     * @param type link type
     */
    public void removePredLink(Task pred, Link.Type type) {
        removeLink(pred, preds, type);
    } // removePredLink
    
    
    /**
     * Allows to remove link to successor
     * @param succ successor task
     * @param type link type
     */
    public void removeSuccLink(Task succ, Link.Type type) {
        removeLink(succ, succs, type);
    } // removeSuccLink
    
    
    private void removeLink(Task task, Map<Link.Id, Link> map, Link.Type type) {
        if (type != null) {
            Link link = map.get(Link.fetchId(task, type));
            if (link == null) {
                throw new IllegalArgumentException();
            } //
            link.remove();
        } else {
            for (Link.Type t : Link.Type.values()) {
                Link link = map.get(Link.fetchId(task, t));
                if (link != null) {
                    link.remove();
                } // if
            } // for
        } // else
    } // removeLink
    
    
    /**
     * Notifies that link to predecessor is removed (when Link#remove is
     * called), so that task could remove the link from its internal map.
     */
    protected void notifyRemovePred(Link link, boolean recalc) {
        succs.remove(link.getIdPred());
        if (recalc) {
            chart.recalc();
        } // if
    } // notifyRemovePred
    
    
    /**
     * Notifies that link to successor is removed (when Link#remove is
     * called), so that task could remove the link from its internal map.
     */
    protected void notifyRemoveSucc(Link link, boolean recalc) {
        succs.remove(link.getIdSucc());
        if (recalc) {
            chart.recalc();
        } // if
    } // notifyRemoveSucc
    
    
    /**
     * Returns the view of the set of keys of the internal map of successor links.
     * @return unmodifiable set of keys
     */
    public Set<Link.Id> getSuccIds() {
        return Collections.unmodifiableSet(succs.keySet());
    } // getSuccIds
    
    
    /**
     * Returns the view of the set of keys of the internal map of predecessor links.
     * @return unmodifiable set of keys
     */
    public Set<Link.Id> getPredIds() {
        return Collections.unmodifiableSet(preds.keySet());
    } // getPredIds
    
    
    /**
     * Returns successor by its id, see {@link #getSuccIds}
     */
    public Link getSucc(Link.Id id) {
        return succs.get(id);
    } // getSucc
    
    
    /**
     * Returns predecessor by its id, see {@link #getPredIds}
     */
    public Link getPred(Link.Id id) {
        return preds.get(id);
    } // getPred
    
    
    /**
     * Returns true if tasks are equal (comparison based on ids)
     */
    @Override
    public boolean equals(Object o) {
        return (o instanceof Task) && ((Task) o).getId() == id;
    } // equals
    
    
    /**
     * Returns tasks hash code (its id)
     */
    @Override
    public int hashCode() {
        return id;
    } // hashCode
    
    
    /**
     * Enumeration of possible ways in which task could participate in
     * critical path (by start, by end, completely or not at all).
     */
    public enum Critical {
        NONE, START, FINISH, FULL
    } // Critical
    
    
} // class Task

